import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Scanner;
import java.util.Timer;
import java.util.TimerTask;

public class ChatServer {
	private static int messagePort;
	private static int intervalMillisecond;
	private static Map<String, Socket> onlines = Collections
			.synchronizedMap(new HashMap<String, Socket>());
	private static Map<String, List<String>> messagesToClient = Collections
			.synchronizedMap(new HashMap<String, List<String>>());
	private static List<String> globalMessages = Collections
			.synchronizedList(new LinkedList<String>());

	public static void main(String args[]) throws IOException {
		loadConfig();

		Runnable messageCenter = new Runnable() {
			public void run() {
				dispatchMessages();
			}
		};
		new Thread(messageCenter).start();

		Runnable messageSender = new Runnable() {
			public void run() {
				sendMessages();
			}
		};
		new Thread(messageSender).start();

		ServerSocket socket = new ServerSocket(messagePort);
		while (true) {
			final Socket connection = socket.accept();
			Runnable client = new Runnable() {
				public void run() {
					try {
						handleConnection(connection);
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			};
			new Thread(client).start();
		}
	}

	private static void loadConfig() throws IOException {
		Properties prop = new Properties();
		FileReader in = null;
		in = new FileReader("./config.properties");
		prop.load(in);

		messagePort = Integer.parseInt(prop.getProperty("messagePort"));
		intervalMillisecond = Integer.parseInt(prop
				.getProperty("intervalMillisecond"));

		in.close();
	}

	private static void dispatchMessages() {
		TimerTask task = new TimerTask() {
			public void run() {
				if (!globalMessages.isEmpty()) {
					String message = globalMessages.remove(0);
					if (message != null) {
						for (List<String> messageQueue : messagesToClient
								.values()) {
							messageQueue.add(message);
						}
					}
				}
			}
		};

		Timer timer = new Timer();
		timer.schedule(task, 0, intervalMillisecond);
	}

	private static void sendMessages() {
		TimerTask task = new TimerTask() {
			public void run() {
				synchronized (onlines) {
					for (Map.Entry<String, Socket> online : onlines.entrySet()) {
						String userName = online.getKey();
						Socket connection = online.getValue();
						PrintWriter out = null;
						try {
							out = new PrintWriter(new OutputStreamWriter(
									connection.getOutputStream(), "UTF8"), true);
						} catch (IOException e) {
							e.printStackTrace();
						}
						List<String> messageQueue = messagesToClient
								.get(userName);
						if (messageQueue != null) {
							while (!messageQueue.isEmpty()) {
								String message = messageQueue.remove(0);
								out.println(message);
							}
						}
					}
				}
			}
		};

		Timer timer = new Timer();
		timer.schedule(task, 0, intervalMillisecond);
	}

	private static void handleConnection(Socket connection) throws IOException {
		Scanner in = new Scanner(connection.getInputStream(), "UTF8");
		PrintWriter out = new PrintWriter(new OutputStreamWriter(
				connection.getOutputStream(), "UTF8"), true);
		SignInStatus signInstatus = null;
		String userName;
		do {
			userName = in.nextLine();
			signInstatus = signIn(userName);
			if (signInstatus == SignInStatus.OK) {
				onlines.put(userName, connection);
				messagesToClient.put(userName,
						Collections.synchronizedList(new LinkedList<String>()));
				globalMessages.add("[" + Calendar.getInstance().getTime()
						+ "] " + "<Room Info> - " + userName + " entered.");
			}
			out.println(signInstatus);
		} while (signInstatus != SignInStatus.OK);

		while (true) {
			String content = null;
			try {
				content = in.nextLine();
			} catch (NoSuchElementException e) {
				signOut(connection, userName);
				globalMessages.add("[" + Calendar.getInstance().getTime()
						+ "] " + "<Room Info> - " + userName + " left.");
				break;
			}
			globalMessages.add("[" + Calendar.getInstance().getTime() + "] "
					+ userName + ": " + content);
		}
	}

	private static void signOut(Socket connection, String userName)
			throws IOException {
		synchronized (onlines) {
			connection.close();
			onlines.remove(userName);
			messagesToClient.remove(userName);
		}
	}

	private static SignInStatus signIn(String userName) {
		if (onlines.containsKey(userName)) {
			return SignInStatus.IN_USE;
		}
		return SignInStatus.OK;
	}
}
